/**
 * Copyright (c) 2024 Hunan OpenValley Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "GPUImageLevelsFilter.h"

const std::string GPUImageLevelsFilter::LEVELS_FRAGMET_SHADER = R"(
    #extension GL_OES_EGL_image_external : require
    varying highp vec2 textureCoordinate;
    
    uniform samplerExternalOES inputImageTexture;
    uniform mediump vec3 levelMinimum;
    uniform mediump vec3 levelMiddle;
    uniform mediump vec3 levelMaximum;
    uniform mediump vec3 minOutput;
    uniform mediump vec3 maxOutput;
    
    void main()
    {
        mediump vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
        
        gl_FragColor = vec4(mix(minOutput, maxOutput, pow(min(max(textureColor.rgb - levelMinimum, vec3(0.0)) / (levelMaximum - levelMinimum), vec3(1.0)), 1.0 / levelMiddle)), textureColor.a);
    }
)";

GPUImageLevelsFilter::GPUImageLevelsFilter()
    : GPUImageFilter(NO_FILTER_VERTEX_SHADER, LEVELS_FRAGMET_SHADER),
      min(new float[3]{0.0f, 0.0f, 0.0f}), mid(new float[3]{1.0f, 1.0f, 1.0f}),
      max(new float[3]{1.0f, 1.0f, 1.0f}),
      minOutput(new float[3]{0.0f, 0.0f, 0.0f}),
      maxOutput(new float[3]{1.0f, 1.0f, 1.0f}) {}

GPUImageLevelsFilter::~GPUImageLevelsFilter()
{
    delete[] min;
    delete[] mid;
    delete[] max;
    delete[] minOutput;
    delete[] maxOutput;
}

GPUImageLevelsFilter::GPUImageLevelsFilter(float *minArray, float *midArray,
                                           float *maxArray, float *minOutArray,
                                           float *maxOutArray)
    : GPUImageFilter(NO_FILTER_VERTEX_SHADER, LEVELS_FRAGMET_SHADER)
{
    min = minArray;
    mid = midArray;
    max = maxArray;
    minOutput = minOutArray;
    maxOutput = maxOutArray;
}

void GPUImageLevelsFilter::onInit()
{
    GPUImageFilter::onInit();
    minLocation = glGetUniformLocation(getProgram(), "levelMinimum");
    midLocation = glGetUniformLocation(getProgram(), "levelMiddle");
    maxLocation = glGetUniformLocation(getProgram(), "levelMaximum");
    minOutputLocation = glGetUniformLocation(getProgram(), "minOutput");
    maxOutputLocation = glGetUniformLocation(getProgram(), "maxOutput");
}

void GPUImageLevelsFilter::onInitialized()
{
    GPUImageFilter::onInitialized();
    setMin(0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
    updateUniforms();
}

void GPUImageLevelsFilter::updateUniforms()
{
    setFloatVec3(minLocation, min);
    setFloatVec3(midLocation, mid);
    setFloatVec3(maxLocation, max);
    setFloatVec3(minOutputLocation, minOutput);
    setFloatVec3(maxOutputLocation, maxOutput);
}

void GPUImageLevelsFilter::setMin(float minVal, float midVal, float maxVal,
                                  float minOutVal, float maxOutVal)
{
    setRedMin(minVal, midVal, maxVal, minOutVal, maxOutVal);
    setGreenMin(minVal, midVal, maxVal, minOutVal, maxOutVal);
    setBlueMin(minVal, midVal, maxVal, minOutVal, maxOutVal);
}

void GPUImageLevelsFilter::setMin(float minVal, float midVal, float maxVal)
{
    setMin(minVal, midVal, maxVal, 0.0f, 1.0f);
}

void GPUImageLevelsFilter::setRedMin(float minVal, float midVal, float maxVal,
                                     float minOutVal, float maxOutVal)
{
    min[0] = minVal;
    mid[0] = midVal;
    max[0] = maxVal;
    minOutput[0] = minOutVal;
    maxOutput[0] = maxOutVal;
    updateUniforms();
}

void GPUImageLevelsFilter::setRedMin(float minVal, float midVal, float maxVal)
{
    setRedMin(minVal, midVal, maxVal, 0.0f, 1.0f);
}

void GPUImageLevelsFilter::setGreenMin(float minVal, float midVal, float maxVal,
                                       float minOutVal, float maxOutVal)
{
    min[1] = minVal;
    mid[1] = midVal;
    max[1] = maxVal;
    minOutput[1] = minOutVal;
    maxOutput[1] = maxOutVal;
    updateUniforms();
}

void GPUImageLevelsFilter::setGreenMin(float minVal, float midVal,
                                       float maxVal)
{
    setGreenMin(minVal, midVal, maxVal, 0.0f, 1.0f);
}

void GPUImageLevelsFilter::setBlueMin(float minVal, float midVal, float maxVal,
                                      float minOutVal, float maxOutVal)
{
    uint8_t blueIndex = 2;
    min[blueIndex] = minVal;
    mid[blueIndex] = midVal;
    max[blueIndex] = maxVal;
    minOutput[blueIndex] = minOutVal;
    maxOutput[blueIndex] = maxOutVal;
    updateUniforms();
}

void GPUImageLevelsFilter::setBlueMin(float minVal, float midVal,
                                      float maxVal)
{
    setBlueMin(minVal, midVal, maxVal, 0.0f, 1.0f);
}